/*           Copyright(c) 2007-2008 Dave Sexton            *
 *                   XmlTreeView Control                   *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.XPath;

namespace DaveSexton.Controls
{
	public class XmlTreeViewDataManager : IXmlTreeViewDataManager
	{
		#region Public Properties
		public virtual bool BindToDataSourceChildren
		{
			get
			{
				return false;
			}
		}

		public virtual IXPathNavigable DataSource
		{
			get
			{
				return dataSource;
			}
			set
			{
				if (dataSource != value)
				{
					dataSource = value;

					OnDataSourceChanged(EventArgs.Empty);
				}
			}
		}
		#endregion

		#region Private / Protected
		private IXPathNavigable dataSource;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="XmlTreeViewDataManager" /> class for derived classes.
		/// </summary>
		protected internal XmlTreeViewDataManager()
		{
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="XmlTreeViewDataManager" /> class for derived classes.
		/// </summary>
		protected XmlTreeViewDataManager(IXPathNavigable dataSource)
		{
			if (dataSource == null)
				throw new ArgumentNullException("dataSource");

			this.dataSource = dataSource;
		}
		#endregion

		#region Methods
		public static string GetAttribute(IXPathNavigable element, string name)
		{
			if (element == null)
				throw new ArgumentNullException("element");

			XPathNavigator navigator = element.CreateNavigator();

			return navigator.GetAttribute(name, "");
		}

		public static void SetAttribute(IXPathNavigable element, string name, string value)
		{
			if (element == null)
				throw new ArgumentNullException("element");

			XPathNavigator navigator = element.CreateNavigator();

			if (navigator.MoveToAttribute(name, ""))
			{
				if (value == null)
					navigator.DeleteSelf();
				else
					navigator.SetValue(value);
			}
			else if (value != null)
				navigator.CreateAttribute(null, name, null, value);
		}

		public virtual void Update()
		{
			OnDataSourceChanged(EventArgs.Empty);
		}

		public virtual void Update(IXPathNavigable originalElement, IXPathNavigable element)
		{
			if (element == null)
				throw new ArgumentNullException("element");

			OnElementChanged(new ElementChangedEventArgs(originalElement, element, ElementChangeType.Updated));
		}

		public virtual void InitializeTreeNode(XmlTreeNode node, IXPathNavigable element)
		{
		}

		public virtual bool ShouldCheckRecursive(XmlTreeNode node, IXPathNavigable element)
		{
			return true;
		}

		public virtual void CheckElement(XmlTreeNode node, IXPathNavigable element, bool check)
		{
			SetAttribute(element, "checked", check.ToString());

			OnElementChanged(new ElementChangedEventArgs(element, ElementChangeType.CheckChanged));
		}

		public virtual bool CanEditElement(XmlTreeNode node, IXPathNavigable element)
		{
			return false;
		}

		public virtual void SetElementLabel(XmlTreeNode node, IXPathNavigable element, string label)
		{
			throw new NotSupportedException();
		}

		public virtual bool CanMoveElement(XmlTreeNode node, IXPathNavigable element)
		{
			return true;
		}

		public virtual bool CanContainElement(XmlTreeNode node, IXPathNavigable element, XmlTreeNode parent, IXPathNavigable parentElement)
		{
			if (element == null)
				throw new ArgumentNullException("element");

			XPathNavigator source = element.CreateNavigator();

			if (parentElement == null)
			// target is the root
			{
				if (BindToDataSourceChildren)
				{
					if (source.MoveToParent())
						// in the default implementation nodes may only be moved among siblings
						return source.IsSamePosition(dataSource.CreateNavigator());
					else
						return false;
				}

				return false;
			}

			XPathNavigator parentNavigator = parentElement.CreateNavigator();

			// in the default implementation nodes may only be moved among siblings
			return source.MoveToParent() && source.IsSamePosition(parentNavigator);
		}

		public virtual bool CanDeleteElement(XmlTreeNode node, IXPathNavigable element)
		{
			return true;
		}

		public virtual bool ShouldDeleteRecursive(XmlTreeNode node, IXPathNavigable element)
		{
			return false;
		}

		public virtual bool DeleteElement(XmlTreeNode node, IXPathNavigable element)
		{
			if (element == null)
				throw new ArgumentNullException("element");

			XPathNavigator source = element.CreateNavigator();

			source.DeleteSelf();

			OnElementChanged(new ElementChangedEventArgs(element, ElementChangeType.Deleted));

			return true;
		}

		public virtual IXPathNavigable InsertNewChild(IXPathNavigable element, string name)
		{
			if (element == null)
				throw new ArgumentNullException("element");

			XPathNavigator navigator = element.CreateNavigator();

			navigator.PrependChildElement(null, name, null, null);
			navigator.MoveToFirstChild();

			OnElementChanged(new ElementChangedEventArgs(navigator, ElementChangeType.Created));

			return navigator;
		}

		public virtual IXPathNavigable InsertElementBefore(IXPathNavigable sourceElement, IXPathNavigable targetElement)
		{
			if (sourceElement == null)
				throw new ArgumentNullException("sourceElement");

			if (targetElement == null)
				throw new ArgumentNullException("targetElement");

			XPathNavigator source = sourceElement.CreateNavigator();
			XPathNavigator target = targetElement.CreateNavigator();

			target.InsertBefore(source);
			source.Clone().DeleteSelf();

			target.MoveToPrevious();

			OnElementChanged(new ElementChangedEventArgs(source, target, ElementChangeType.Moved));

			return target;
		}

		public virtual IXPathNavigable InsertElementAfter(IXPathNavigable sourceElement, IXPathNavigable targetElement)
		{
			if (sourceElement == null)
				throw new ArgumentNullException("sourceElement");

			if (targetElement == null)
				throw new ArgumentNullException("targetElement");

			XPathNavigator source = sourceElement.CreateNavigator();
			XPathNavigator target = targetElement.CreateNavigator();

			target.InsertAfter(source);
			source.Clone().DeleteSelf();

			target.MoveToNext();

			OnElementChanged(new ElementChangedEventArgs(source, target, ElementChangeType.Moved));

			return target;
		}

		public virtual IXPathNavigable InsertElementFirstChild(IXPathNavigable sourceElement, IXPathNavigable targetElement)
		{
			if (sourceElement == null)
				throw new ArgumentNullException("sourceElement");

			if (targetElement == null)
				throw new ArgumentNullException("targetElement");

			XPathNavigator source = sourceElement.CreateNavigator();
			XPathNavigator target = targetElement.CreateNavigator();

			target.PrependChild(source);
			source.Clone().DeleteSelf();

			target.MoveToFirstChild();

			OnElementChanged(new ElementChangedEventArgs(source, target, ElementChangeType.Moved));

			return target;
		}

		public virtual IXPathNavigable InsertElementLastChild(IXPathNavigable sourceElement, IXPathNavigable targetElement)
		{
			if (sourceElement == null)
				throw new ArgumentNullException("sourceElement");

			if (targetElement == null)
				throw new ArgumentNullException("targetElement");

			XPathNavigator source = sourceElement.CreateNavigator();
			XPathNavigator target = targetElement.CreateNavigator();

			target.AppendChild(source);
			source.Clone().DeleteSelf();

			target.MoveToFirstChild();

			while (target.MoveToNext(XPathNodeType.Element))
			{
				// do nothing
			}

			OnElementChanged(new ElementChangedEventArgs(source, target, ElementChangeType.Moved));

			return target;
		}

		public virtual IXPathNavigable ReplaceElement(IXPathNavigable current, IXPathNavigable replacement)
		{
			if (current == null)
				throw new ArgumentNullException("current");

			if (replacement == null)
				throw new ArgumentNullException("replacement");

			XPathNavigator navigator = current.CreateNavigator();
			XPathNavigator original = navigator.Clone();

			navigator.ReplaceSelf(replacement.CreateNavigator());

			OnElementChanged(new ElementChangedEventArgs(original, navigator, ElementChangeType.Replaced));

			return navigator;
		}
		#endregion

		#region Events
		private readonly object DataSourceChangedEventLock = new object();
		private EventHandler DataSourceChangedEvent;
		private readonly object ElementChangedEventLock = new object();
		private EventHandler<ElementChangedEventArgs> ElementChangedEvent;

		/// <summary>
		/// Event raised when an element changes, notifying the <see cref="XmlTreeView"/> to update the bound node.
		/// </summary>
		public event EventHandler<ElementChangedEventArgs> ElementChanged
		{
			add
			{
				lock (ElementChangedEventLock)
				{
					ElementChangedEvent += value;
				}
			}
			remove
			{
				lock (ElementChangedEventLock)
				{
					ElementChangedEvent -= value;
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="ElementChanged" /> event.
		/// </summary>
		/// <param name="e"><see cref="EventArgs" /> object that provides the arguments for the event.</param>
		protected virtual void OnElementChanged(ElementChangedEventArgs e)
		{
			EventHandler<ElementChangedEventArgs> handler = null;

			lock (ElementChangedEventLock)
			{
				handler = ElementChangedEvent;
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the <see cref="DataSource"/> property value changes.
		/// </summary>
		public event EventHandler DataSourceChanged
		{
			add
			{
				lock (DataSourceChangedEventLock)
				{
					DataSourceChangedEvent += value;
				}
			}
			remove
			{
				lock (DataSourceChangedEventLock)
				{
					DataSourceChangedEvent -= value;
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="DataSourceChanged" /> event.
		/// </summary>
		/// <param name="e"><see cref="EventArgs" /> object that provides the arguments for the event.</param>
		protected virtual void OnDataSourceChanged(EventArgs e)
		{
			EventHandler handler = null;

			lock (DataSourceChangedEventLock)
			{
				handler = DataSourceChangedEvent;
			}

			if (handler != null)
				handler(this, e);
		}
		#endregion
	}
}
